Skip to content

fix(vite): clear module runner cache before dev worker reload - #4473

Open
brenelz wants to merge 2 commits into
nitrojs:mainfrom
brenelz:fix/vite-dev-worker-clear-evaluated-modules
Open

fix(vite): clear module runner cache before dev worker reload#4473
brenelz wants to merge 2 commits into
nitrojs:mainfrom
brenelz:fix/vite-dev-worker-clear-evaluated-modules

Conversation

@brenelz

@brenelz brenelz commented Jul 23, 2026

Copy link
Copy Markdown

🔗 Linked issue

Resolves item (3) of #4472. Same root cause as #4020 (closed, but the fix never landed). Related to #4043.

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme, or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

ViteEnvRunner.reload() in the dev worker re-imports the environment entry without clearing the ModuleRunner's evaluatedModules cache.

The runner's fetchModule protocol answers {cache: true} for any module whose transform is already populated on the Vite side at fetch time (packages/vite's fetchModule: if (options.cached && cached) return { cache: true }). During a dev-worker reload, plugin-side crawls can repopulate transforms for large parts of the graph before the runner re-fetches them (observed with vite-plugin-solid's ?assets transform), so the re-import walks the entry but keeps stale evaluations for the very modules that changed — the server renders old code until the dev server is restarted, causing SSR/client hydration mismatches.

Vite's own full-reload handler in the module runner clears evaluatedModules before re-importing (hmrHandler.ts); this PR does the same in the worker's reload().

Verified against a Vite 8 + vite-plugin-solid SSR app: with this change (plus a full-reload actually being sent for shared modules — see #4472 items 1–2), edits to SSR-rendered modules are reflected on the next request without restarting the dev server, and hydration is clean. NITRO_DEBUG=1 traces confirm all app modules re-execute after reload instead of being served from the stale cache.

Includes a regression test in the vite:hmr suite: the fixture gains a dep-crawler plugin that re-transforms dep.ts as a side effect of transforming api/crawled.ts (the way asset-collecting plugins crawl imports), plus a plugin that hard-invalidates the server module graph on dep.ts edits (as framework staleness workarounds do in the wild). Without evaluatedModules.clear() the crawled transform makes fetchModule answer {cache: true} for dep.ts and the test times out on the stale response; with the fix it passes.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@brenelz
brenelz requested a review from pi0 as a code owner July 23, 2026 03:27
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

@brenelz is attempting to deploy a commit to the Nitro Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Vite reloads now clear evaluated module state before re-importing the entry module. HMR fixtures and plugins reproduce dependency crawling and graph invalidation, while a regression test verifies updated API output and a full-reload message.

Changes

Vite reload cache handling

Layer / File(s) Summary
Reset evaluated modules during reload
src/runtime/internal/vite/dev-worker.mjs
ViteEnvRunner.reload() clears evaluated modules before re-importing the entry module.
Exercise dependency graph invalidation
test/vite/hmr-fixture/dep.ts, test/vite/hmr-fixture/api/crawled.ts, test/vite/hmr-fixture/vite.config.ts
The HMR fixture adds a dependency value, a consuming API module, and plugins that crawl and invalidate the server module graph.
Validate refreshed dependency output
test/vite/hmr.test.ts
The regression test edits dep.ts, verifies the crawled API returns the new value, and checks for a full-reload message.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

  • nitrojs/nitro#3836 — Also changes environment-runner reload behavior in the internal Vite runner.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows conventional commits and accurately describes the cache-clearing reload fix.
Description check ✅ Passed The description directly matches the PR's Vite dev worker reload cache fix and regression test.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pi0

pi0 commented Jul 23, 2026

Copy link
Copy Markdown
Member

Would be nice to add a regression test to vite-hmr fixture!

@pkg-pr-new

pkg-pr-new Bot commented Jul 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/nitro@4473

commit: b6bcf72

brenelz added 2 commits July 23, 2026 05:26
The dev worker's ViteEnvRunner.reload() re-imports the environment entry
without clearing the ModuleRunner's evaluatedModules cache. When a module's
transform is already populated on the Vite side at fetch time (for example
re-transformed by an assets crawl during the reload itself), fetchModule
answers {cache: true} and the runner reuses the stale evaluation, so the
server keeps executing old code until the dev server is restarted.

Vite's own full-reload handler clears evaluatedModules before re-importing;
do the same here.
The hmr fixture gains a dep-crawler plugin that re-transforms dep.ts as a
side effect of transforming api/crawled.ts (the way asset-collecting
plugins crawl imports), plus a plugin that hard-invalidates the server
module graph on dep.ts edits (as framework staleness workarounds do). On a
reload without evaluatedModules.clear(), the crawled transform makes
fetchModule answer {cache: true} for dep.ts and the stale evaluation is
served until restart — the new test fails without the fix and passes with
it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants